using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Collections;

namespace Decav.Collections.ObjectModel
{

    /// <summary>
    /// A dictionary that provides events for when items are added or removed from the dictionary.
    /// </summary>
    /// <remarks>
    /// Since the generic dictionary class is not inheritable, this class implements generic IDictionary and forwards most calls to
    /// the underlying Dictionary object.
    /// </remarks>
    /// <typeparam name="K">The type of key.</typeparam>
    /// <typeparam name="V">The type of value.</typeparam>
    [Serializable]
    public class WatchableDictionary<K, V> : IDictionary<K, V>
    {

        #region Field Declarations

        /// <summary>
        /// The inner dictionary that contains the keys and values in this dictionary.
        /// </summary>
        protected readonly Dictionary<K, V> InnerDictionary = new Dictionary<K, V>( );

        /// <summary>
        /// A read only wrapper for this dictionary if one has been generated.
        /// </summary>
        /// <remarks>
        /// Used by the implicit conversion so the same instance will always be returned.
        /// </remarks>
        private WatchableReadOnlyDictionary<K, V> _wrapper;

        #endregion

        #region Event Declarations

        /// <summary>
        /// Event that is raised when an item is added to the collection.
        /// </summary>
        [Description( "Event that is raised when an item is added to the collection." )]
        public event ItemAddedEventHandler<KeyValuePair<K, V>> Added;

        /// <summary>
        /// Event that is raised when an item is removed from the collection.
        /// </summary>
        [Description( "Event that is raised when an item is removed from the collection." )]
        public event ItemRemovedEventHandler<KeyValuePair<K, V>> Removed;

        /// <summary>
        /// Event that is raised when an existing item in the list is modified.
        /// </summary>
        [Description( "Event that is raised when an existing item in the list is modified." )]
        public event ItemModifiedEventHandler<KeyValuePair<K, V>> Modified;

        /// <summary>
        /// Event that is raised when the collection is cleared of all items.
        /// </summary>
        [Description( "Event that is raised when the collection is cleared of all items." )]
        public event EventHandler Cleared;

        #endregion

        #region Collection Members

        /// <summary>
        /// Gets the number of elements contained in the dictionary.
        /// </summary>
        /// <value></value>
        /// <returns>The number of elements contained in the dictionary.</returns>
        public int Count
        {
            get
            {
                return InnerDictionary.Count;
            }
        }

        /// <summary>
        /// Gets the System.Collections.Generic.IEqualityComparer that is used to determine equality of keys for the dictionary.
        /// </summary>
        /// <remarks>
        /// The System.Collections.Generic.IEqualityComparer generic interface implementation
        /// that is used to determine equality of keys for the current System.Collections.Generic.Dictionary
        /// and to provide hash values for the keys.
        /// </remarks>
        public IEqualityComparer<K> Comparer
        {
            get
            {
                return InnerDictionary.Comparer;
            }
        }

        /// <summary>
        /// Wraps this dictionary with a read only wrapper.
        /// </summary>
        /// <returns>A read only wrapper around this dictionary. Multiple casts will yield the same wrapper.</returns>
        public WatchableReadOnlyDictionary<K, V> AsReadOnly( )
        {
            return _wrapper ?? ( _wrapper = new WatchableReadOnlyDictionary<K, V>( this ) );
        }

        /// <summary>
        /// Removes all items from the dictionary.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">The collection is read-only. </exception>
        public void Clear( )
        {
            ClearItems( );
        }

        /// <summary>
        /// Inserts the specified key value pair into the dictionary.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        protected virtual void InsertItem( K key, V value )
        {

            // Let the dictionary do the real work
            InnerDictionary.Add( key, value );

            // Raise the event
            OnAdded( new ItemAddedEventArgs<KeyValuePair<K, V>>( new KeyValuePair<K, V>( key, value ) ) );

        }

        /// <summary>
        /// Removes an item from the dictionary using the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>True if the item was removed, false otherwise.</returns>
        protected virtual bool RemoveItem( K key )
        {

            // Find the item that we're removing
            V removedValue;
            if ( InnerDictionary.TryGetValue( key, out removedValue ) )
            {

                // Let the dictionary do the real work
                bool retVal = InnerDictionary.Remove( key );

                if ( retVal )
                {
                    // Raise the event
                    OnRemoved( new ItemRemovedEventArgs<KeyValuePair<K, V>>( new KeyValuePair<K, V>( key, removedValue ) ) );
                }   // if

                return retVal;

            }   // if
            else
            {

                // Item was not found
                return false;

            }   // else

        }

        /// <summary>
        /// Replaces the item with the given key with a new value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="newValue">The new value.</param>
        protected virtual void SetItem( K key, V newValue )
        {

            V replacedValue;
            if ( InnerDictionary.TryGetValue( key, out replacedValue ) )
            {

                // Item exists, replace it
                InnerDictionary[key] = newValue;

                // Raise the modified event
                OnModified( new ItemModifiedEventArgs<KeyValuePair<K, V>>( new KeyValuePair<K, V>( key, replacedValue ), new KeyValuePair<K, V>( key, newValue ) ) );

            }   // if
            else
            {

                // Add the item (this will raise the add event)
                InsertItem( key, newValue );

            }   // else

        }

        /// <summary>
        /// Clears the items in the dictionary.
        /// </summary>
        protected virtual void ClearItems( )
        {

            // Let the dictionary do the real work
            InnerDictionary.Clear( );

            // Raise the event
            OnCleared( EventArgs.Empty );

        }

        #endregion

        #region Event Invoke Methods

        /// <summary>
        /// Raises the Added event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnAdded( ItemAddedEventArgs<KeyValuePair<K, V>> e )
        {

            // Invoke the event handlers
            if ( Added != null )
            {
                Added( this, e );
            }   // if

        }

        /// <summary>
        /// Raises the Removed event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnRemoved( ItemRemovedEventArgs<KeyValuePair<K, V>> e )
        {

            // Invoke the event handlers
            if ( Removed != null )
            {
                Removed( this, e );
            }   // if

        }

        /// <summary>
        /// Raises the Modified event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnModified( ItemModifiedEventArgs<KeyValuePair<K, V>> e )
        {

            // Invoke the event handlers
            if ( Modified != null )
            {
                Modified( this, e );
            }   // if

        }

        /// <summary>
        /// Raises the Cleared event to event subscribers.
        /// </summary>
        /// <remarks>
        /// Note to inheritors: You must call the base method in order to ensure that the event is raised and clean up
        /// work is properly performed.
        /// </remarks>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnCleared( EventArgs e )
        {

            // Invoke the event handlers
            if ( Cleared != null )
            {
                Cleared( this, e );
            }   // if

        }

        #endregion

        #region IDictionary<K,V> Members

        /// <summary>
        /// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </summary>
        /// <param name="key">The object to use as the key of the element to add.</param>
        /// <param name="value">The object to use as the value of the element to add.</param>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"></see> is read-only.</exception>
        /// <exception cref="T:System.ArgumentException">An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</exception>
        /// <exception cref="T:System.ArgumentNullException">key is null.</exception>
        public void Add( K key, V value )
        {
            InsertItem( key, value );
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"></see> contains an element with the specified key.
        /// </summary>
        /// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</param>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.IDictionary`2"></see> contains an element with the key; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">key is null.</exception>
        public bool ContainsKey( K key )
        {
            return InnerDictionary.ContainsKey( key );
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </summary>
        /// <value></value>
        /// <returns>An <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</returns>
        public ICollection<K> Keys
        {
            get
            {
                return new ReadOnlyCollection<K>( new List<K>( InnerDictionary.Keys ) );
            }
        }

        /// <summary>
        /// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <returns>
        /// true if the element is successfully removed; otherwise, false.  This method also returns false if key was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"></see> is read-only.</exception>
        /// <exception cref="T:System.ArgumentNullException">key is null.</exception>
        public bool Remove( K key )
        {
            return RemoveItem( key );
        }

        /// <summary>
        /// Gets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key whose value to get.</param>
        /// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the 
        /// type of the value parameter. This parameter is passed uninitialized.</param>
        /// <returns>true if the object that implements IDictionary contains an element with the specified key; otherwise, false.</returns>
        public bool TryGetValue( K key, out V value )
        {
            return InnerDictionary.TryGetValue( key, out value );
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"></see>.
        /// </summary>
        /// <value></value>
        /// <returns>An <see cref="T:System.Collections.Generic.ICollection`1"></see> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"></see>.</returns>
        public ICollection<V> Values
        {
            get
            {
                return new ReadOnlyCollection<V>( new List<V>( InnerDictionary.Values ) );
            }
        }

        /// <summary>
        /// Gets or sets the element with the specified key.
        /// </summary>
        /// <param name="key">The key of the element to get or set.</param>
        /// <returns>The element with the specified key.</returns>
        public V this[K key]
        {
            get
            {
                return InnerDictionary[key];
            }
            set
            {
                SetItem( key, value );
            }
        }

        #endregion

        #region ICollection<KeyValuePair<K,V>> Members

        /// <summary>
        /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </summary>
        /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
        void ICollection<KeyValuePair<K, V>>.Add( KeyValuePair<K, V> item )
        {
            InsertItem( item.Key, item.Value );
        }

        /// <summary>
        /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only. </exception>
        void ICollection<KeyValuePair<K, V>>.Clear( )
        {
            ClearItems( );
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> contains a specific value.
        /// </summary>
        /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
        /// <returns>
        /// true if item is found in the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false.
        /// </returns>
        bool ICollection<KeyValuePair<K, V>>.Contains( KeyValuePair<K, V> item )
        {
            return InnerDictionary.ContainsKey( item.Key );
        }

        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">arrayIndex is less than 0.</exception>
        /// <exception cref="T:System.ArgumentNullException">array is null.</exception>
        /// <exception cref="T:System.ArgumentException">array is multidimensional.-or-arrayIndex is equal to or greater than the length of array.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"></see> is greater than the available space from arrayIndex to the end of the destination array.-or-Type T cannot be cast automatically to the type of the destination array.</exception>
        void ICollection<KeyValuePair<K, V>>.CopyTo( KeyValuePair<K, V>[] array, int arrayIndex )
        {
            ( (ICollection<KeyValuePair<K, V>>)InnerDictionary ).CopyTo( array, arrayIndex );
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </summary>
        /// <value></value>
        /// <returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</returns>
        int ICollection<KeyValuePair<K, V>>.Count
        {
            get
            {
                return InnerDictionary.Count;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only; otherwise, false.</returns>
        bool ICollection<KeyValuePair<K, V>>.IsReadOnly
        {
            get
            {
                return ( (ICollection<KeyValuePair<K, V>>)InnerDictionary ).IsReadOnly;
            }
        }

        /// <summary>
        /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </summary>
        /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
        /// <returns>
        /// true if item was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false. This method also returns false if item is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"></see>.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
        bool ICollection<KeyValuePair<K, V>>.Remove( KeyValuePair<K, V> item )
        {
            return RemoveItem( item.Key );
        }

        #endregion

        #region IEnumerable<KeyValuePair<K,V>> Members

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
        /// </returns>
        IEnumerator<KeyValuePair<K, V>> IEnumerable<KeyValuePair<K, V>>.GetEnumerator( )
        {
            return ( (IEnumerable<KeyValuePair<K, V>>)InnerDictionary ).GetEnumerator( );
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator( )
        {
            return ( (IEnumerable)InnerDictionary ).GetEnumerator( );
        }

        #endregion

        #region Operators and Conversions

        /// <summary>
        /// Implicitly converts the specified dictionary to a read only wrapper.
        /// </summary>
        /// <param name="dictionary">The dictionary to wrap.</param>
        /// <returns>A read only wrapper around the specified dictionary. Multiple casts will yield the same wrapper.</returns>
        public static implicit operator WatchableReadOnlyDictionary<K, V>( WatchableDictionary<K, V> dictionary )
        {
            return dictionary == null ? null : dictionary.AsReadOnly( );
        }

        #endregion

    }   // class

}   // namespace
